package biolabs.mcl

import biolabs.core.{Application, Delegate, InterpreterSuccess, OutcomeHandler, ScalaError}
import biolabs.mcl.syntax.SingleTypeAST

import java.lang.reflect.Method
import java.lang.reflect.Modifier
import java.lang.reflect.ParameterizedType
import java.lang.reflect.Type
import java.lang.reflect.TypeVariable

import scala.collection.{Map => AMap}

object ActualType {

	def apply(t: Type): ActualType =
		apply(t, Nil)

	def apply(t: Type, typeVarsInCreation: List[TypeVar]): ActualType = {

		val (clazz, typeArgs) = t match {
			case c: Class[_] =>
				(c, c.getTypeParameters())
			case p: ParameterizedType =>
				(p.getRawType().asInstanceOf[Class[_]], p.getActualTypeArguments())
			case tv: TypeVariable[_] =>
				throw new IllegalArgumentException("Cannot convert type variable " + t + " to ActualType")
			case _ =>
				throw new IllegalArgumentException("Cannot convert " + t + " (" + t.getClass() + ") to ActualType")
		}

		// convert Java type arguments to MultiTypes
		val mTypeArgs = typeArgs map (t => MultiType(SingleType(t, typeVarsInCreation)))

		apply(clazz, mTypeArgs.toList)
	}

	def apply(rawType: Class[_], typeArguments: List[MultiType]) =
		new ActualType(rawType.getName(), rawType, typeArguments)

	def analyze(ast: SingleTypeAST, model: ModelDefinition, resolvedTypeVars: AMap[String, TypeVar], app: Application): AnalysisResult[ActualType] = {

		// check that the type exists
		// and that a correct number of type arguments were provided
		val clazz = getClassOf(ast.id, ast.typeArgs.length, app) match {
			case AnalysisSuccess(cls) => cls
			case AnalysisError(msg) => return AnalysisError(msg)
		}

		// get type arguments
		val typeArgs = {
			val (suc, err) = ast.typeArgs map { MultiType.analyze(_, model, resolvedTypeVars, app) } partition { _.successful }
			if(!err.isEmpty)
				return AnalysisError(err map { _.getErrorMessage() } mkString "\n")
			suc map { _.getResult() }
		}

		AnalysisSuccess(new ActualType(ast.id, clazz, typeArgs))
	}

	private def getClassOf(name: String, nArgs: Int, app: Application): AnalysisResult[Class[_]] = {

		// replace type arguments with "_", because they might
		// use type variables not yet known to the interpreter
		val classStr = nArgs match {
			case 0 =>
				name
			case _ =>
				name + (1 to nArgs map (a => "_") mkString("[", ",", "]"))
		}

		app.evalExpr("classOf[" + classStr + "]") match {
			case InterpreterSuccess(value, _, _) => AnalysisSuccess(value.get.asInstanceOf[Class[_]])
			case ScalaError(_, output, _) => AnalysisError(output)
		}
	}
}

/** Represents a type in a unified manner, substitutes both
  * Class and ParameterizedType classes.
  */
class ActualType protected (val name: String, val rawType: Class[_], val typeArguments: List[MultiType]) extends SingleType {

	{
		// check that the numbers of expected and provided type parameters match
		val paramCount = rawType.getTypeParameters().length
		val argCount = typeArguments.length
		if(paramCount != argCount)
			throw new IllegalArgumentException(rawType.getName() +
					" takes " + paramCount + " type parameters, but " +
					argCount + " were provided.")
	}

	// create a map of type arguments
	val typeArgumentMap = rawType.getTypeParameters() map { _.getName() } zip typeArguments toMap

	lazy val supertypes = {
		val interfaces = (rawType.getGenericInterfaces() map { ActualType(_).substituteTypeVariables(typeArgumentMap) }).toList
		rawType.getGenericSuperclass() match {
			case null => interfaces
			case t => ActualType(t).substituteTypeVariables(typeArgumentMap) :: interfaces
		}
	}

	def substituteTypeVariables(substitutions: Map[String, MultiType]): ActualType = {
		val newArgs = typeArguments map { _.substituteTypeVariables(substitutions) }
		new ActualType(name, rawType, newArgs)
	}

	override def getTypeArgumentsOf(ancestor: Class[_]): List[MultiType] = {
		if(!isDescendantOf(ancestor))
			throw new IllegalArgumentException(ancestor + " is not an ancestor of " + rawType)
		if(ancestor.equals(rawType))
			return typeArguments
		for(supertype <- supertypes) {
			if(supertype.isDescendantOf(ancestor))
				return supertype.getTypeArgumentsOf(ancestor)
		}
		throw new AssertionError("unreachable code");
	}

	override def isDescendantOf(ancestor: Class[_]): Boolean =
		ancestor.isAssignableFrom(rawType)

	override def isAssignableFrom(sub: SingleType): Boolean =
		sub match {
			case t: ActualType =>
				if(!rawType.isAssignableFrom(t.rawType))
					return false;
				val subArgs = t.getTypeArgumentsOf(rawType)
				for((arg, subArg) <- typeArguments zip subArgs)
					if(arg != subArg)
						return false
				return true
			case tv: TypeVar =>
				tv.upperBounds exists { isAssignableFrom(_) }
			case _ =>
				throw new IllegalArgumentException(sub + " is neither an actual type nor a type variable")
		}

	def getMethod(name: String, parameterTypes: Array[SingleType]): Option[Method] = {
		val method = rawType.getMethods() filter
			{ _.getName() == name } filter
			{ _.getParameterTypes().length == parameterTypes.length } filter
			(m => ( m.getGenericParameterTypes() map { SingleType(_) }).corresponds(parameterTypes)((a,b) => a==b) )

		if(method.isEmpty)
			None
		else
			Some(method.head)
	}

	lazy val progressApprovers = analyzeActionInterceptors(ActionInterceptor.ProgressApprover)
	lazy val progressHandlers = analyzeActionInterceptors(ActionInterceptor.ProgressHandler)
	lazy val resultHandlers = analyzeActionInterceptors(ActionInterceptor.ResultHandler)

	private def analyzeActionInterceptors[I <: ActionInterceptor](
			it: ActionInterceptor.InterceptorType[I]
	): Iterable[I] = {
		// Get all annotated public methods of clazz.
		// NOTE: We require that the methods are public, because there is
		// no direct way to obtain all (incl. inherited) protected methods.
		val methods = this.rawType.getMethods() filter { it.getAction(_).isDefined }

		// check that the methods have correct return type
		methods filterNot { _.getReturnType() == it.returnType } match {
			case Array() => // OK, do nothing
			case array => throw new SemanticException(
					"Expected return type is " + it.returnType.getName() + ":\n" +
					array.mkString("\t", "\n\t", "\n"))
		}

		// check that the methods take exactly 1 argument
		val msg = methods filter { _.getParameterTypes().length != 1 } match {
			case Array() => // OK, do nothing
			case array => throw new SemanticException("Progress approvers, " +
					"progress handlers and result handlers must take " +
					"exactly 1 parameter:\n" + array.mkString("\t", "\n\t", "\n"))
		}

		// reconstruct action types from annotation arguments
		val actions = {
			val (types, errors) = methods map (m => {
				val (classes, typeParamRefs) = it.getAction(m).get
				try {
					Left(SingleType.reconstruct(classes, typeParamRefs, this.typeArgumentMap))
				} catch {
					case e => Right(e)
				}
			}) partition { _.isLeft }
			if(!errors.isEmpty)
				throw new SemanticException(errors map { _.right.get.getMessage } mkString "\n")
			types map { _.left.get }
		}

		// Resolve expected interceptors' parameter types,
		// i.e. actions' progress types or actions' result types.
		val expectedParamTypes = actions map { it.getParameterTypeFor(_) }

		// Check that actual parameter types of the interceptor
		// methods are supertypes of the expected parameter types.
		{
			val msg = methods zip expectedParamTypes filter { case (m, t) =>
				val actualParamType = m.getGenericParameterTypes()(0)
				!ActualType(actualParamType).isAssignableFrom(t)
			} map { case (m, t) =>
				m + ": parameter type " + m.getGenericParameterTypes()(0) +
				" is not a supertype of action's progress type " + t
			} mkString "\n"
			if(msg != "")
				throw new SemanticException(msg)
		}

		(methods, actions, expectedParamTypes).zipped.toIterable map {
			case (m, a, t) => it(m, a, t)
		}
	}

	lazy val delegateCandidates: List[DelegateMethod] = {
		// get candidates inherited from supertypes
		val inheritedCandidates = supertypes flatMap { _.delegateCandidates }

		// return declared candidates and inherited candidates that are not shadowed by any declared candidate
		declaredDelegateCandidates ::: (inheritedCandidates filter
			(ic => !declaredDelegateCandidates.exists {_.interaction == ic.interaction})
		)
	}

	private lazy val declaredDelegateCandidates = {
		// get all declared methods annotated as delegates
		val methods = rawType.getDeclaredMethods().toList filter { _.getAnnotation(classOf[Delegate]) != null }

		// check that the methods take no argument
		methods filter { _.getParameterTypes().length != 0 } match {
			case Nil => // OK, do nothing
			case list => throw new SemanticException("Delegate methods must not take any parameter: " + (list mkString ", "))
		}

		// check that the methods are not private
		methods filter (m => Modifier.isPrivate(m.getModifiers())) match {
			case Nil => // OK, do nothing
			case list => throw new SemanticException("Delegate methods must not be private: " + (list mkString ", "))
		}

		// reconstruct interaction types from annotations
		val annotations = methods map { _.getAnnotation(classOf[Delegate]) }
		val actions = reconstructInteractionTypes(annotations)

		actions zip methods map { case (a, m) => new DelegateMethod(a, m) }
	}

	override def getDelegateCandidates(interaction: ActualType): List[DelegateMethod] = {
		// get candidates declared in this type
		val declaredCandidates = getDeclaredDelegateCandidates(interaction)

		// get candidates inherited from supertypes
		val inheritedCandidates = supertypes flatMap { _.getDelegateCandidates(interaction) }

		// return declared candidates and inherited candidates that are not shadowed by any declared candidate
		declaredCandidates ::: (inheritedCandidates filter
			(ic => !declaredCandidates.exists {_.interaction.isAssignableFrom(ic.interaction)})
		)
	}

	/** Structural type for action description annotations
	  * (@Delegate, @OutcomeHandler).
	  */
	private type ActionAnnotation = {
		def action(): Array[Class[_]]
		def typeParamRefs(): Array[String]
	}
	private def reconstructInteractionTypes(annotations: List[ActionAnnotation]) = {
		val (acts, errors) = annotations map (a =>
			try {
				Left(SingleType.reconstruct(a.action(), a.typeParamRefs(), typeArgumentMap))
			} catch {
				case e => Right(e)
			}
		) partition { _.isLeft }
		if(!errors.isEmpty)
			throw new SemanticException("Multiple errors occurred", errors map { _.right.get } toArray)
		acts map { _.left.get }
	}

	private def getDeclaredDelegateCandidates(interaction: ActualType): List[DelegateMethod] = {

		// get candidates whose interaction is a subtype of the interaction of interest
		val candidates = declaredDelegateCandidates filter (dm => interaction.isAssignableFrom(dm.interaction))

		// keep only the most general delegates (whose interaction is not a subtype of any other delegate's interaction)
		mostGeneralCandidates(candidates)
	}

	private def mostGeneralCandidates(candidates: List[DelegateMethod]): List[DelegateMethod] = {
		val mostGeneralActions = mostGeneralTypes(candidates map { _.interaction })
		candidates filter (dm => mostGeneralActions.contains(dm.interaction))
	}

	private def mostGeneralTypes(types: List[MultiType]): List[MultiType] = {
		types match {
			case Nil => Nil
			case head::tail =>
				if(tail.exists({ _.isAssignableFrom(head) }))
					mostGeneralTypes(tail)
				else
					head::mostGeneralTypes(tail)
		}
	}

	lazy val outcomeHandlers = getOutcomeHandlers()

	// XXX: misuse of DelegateMethod, should be a separate type
	private def getOutcomeHandlers(): List[DelegateMethod] = {
		val declaredHandlers = getDeclaredOutcomeHandlers()
		val inheritedHandlers = supertypes flatMap { _.getOutcomeHandlers() }
		declaredHandlers ++ (inheritedHandlers filter (ih =>
			!declaredHandlers.exists({_.method.getName == ih.method.getName}))
		)
	}

	private def getDeclaredOutcomeHandlers(): List[DelegateMethod] = {
		// get all declared methods annotated as outcome handlers
		val methods = rawType.getDeclaredMethods().toList filter { _.getAnnotation(classOf[OutcomeHandler]) != null }

		// check that the methods take exactly one argument
		methods filter { _.getParameterTypes().length != 1 } match {
			case Nil => // OK, do nothing
			case list => throw new SemanticException("Outcome handlers must take exactly one parameter: " + (list mkString ", "))
		}

		// check that the methods are not private
		methods filter (m => Modifier.isPrivate(m.getModifiers())) match {
			case Nil => // OK, do nothing
			case list => throw new SemanticException("Outcome handlers must not be private: " + (list mkString ", "))
		}

		// reconstruct interaction types from annotations
		val annotations = methods map { _.getAnnotation(classOf[OutcomeHandler]) }
		val actions = reconstructInteractionTypes(annotations)

		// compatibility of the parameter type will be checked
		// by the Scala compiler when the method is used

		actions zip methods map { case (a, m) => new DelegateMethod(a, m) }
	}

	override def hashCode() =
		rawType.hashCode()

	override def equals(other: Any) = other match {
		case that: ActualType => isEqualTo(that, Nil)
		case _ => false
	}

	override def isEqualTo(other: SingleType, comparing: List[(TypeVar, TypeVar)]) = other match {
		case that: ActualType => isEqualTo(that, comparing)
		case _ => false
	}

	private def isEqualTo(that: ActualType, comparing: List[(TypeVar, TypeVar)]) = {
		name == that.name && rawType == that.rawType && typeArguments.corresponds(that.typeArguments)((a, b) => a.isEqualTo(b, comparing))
	}
}
